package com.boarsoft.dao.sharding.advice;

import java.lang.reflect.Method;

import com.boarsoft.dao.sharding.aspect.DaoShardingPolicy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.boarsoft.common.Util;
import com.boarsoft.dao.sharding.bean.DaoSharding;
import com.boarsoft.dao.sharding.bean.ShardingParam;
import com.boarsoft.dao.sharding.datasource.DynamicDataSource;

public class DaoShardingAdviceImpl implements DaoShardingAdvice {
	private static final Logger log = LoggerFactory.getLogger(DaoShardingAdviceImpl.class);

	private DaoShardingPolicy daoShardingPolicy;

	@Override
	public Object doAround(ProceedingJoinPoint jp) throws Throwable {
		//策略
		String policy = null;
		//分片索引
		int index = -1;
		//值
		String value = null;
		//键
		Object key = null;
		//目标方法
		Object target = jp.getTarget();
		//获得目标类
		Class<?> tc = target.getClass();
		//
		if (tc.isAnnotationPresent(DaoSharding.class)) {
			DaoSharding a = tc.getAnnotation(DaoSharding.class);
			//得到策略
			policy = a.policy();
			// index = a.index(); //用在类上时，index无效
			value = a.value();
		}
		//获取方法签名
		Signature s = jp.getSignature();
		MethodSignature ms = (MethodSignature) s;
		Method method = tc.getMethod(ms.getName(), ms.getParameterTypes());
		//
		if (method.isAnnotationPresent(DaoSharding.class)) {
			DaoSharding a = method.getAnnotation(DaoSharding.class);
			if (Util.strIsNotEmpty(a.policy())) {
				policy = a.policy();
			}
			if (Util.strIsNotEmpty(a.value())) {
				value = a.value();
			}
			//分片索引标识了在第几个参数
			index = a.index();
			if (index >= 0) {
				Object[] args = jp.getArgs();
				if (args == null) {
					throw new IllegalStateException(String.format(//
							"@DaoSharding is not allowed for method %s because it has no arguments", //
							method.toString()));
				}
				if (index >= args.length) {
					throw new IllegalStateException(String.format(//
							"@DaoSharding.index is greater than arguments.length of method %s", //
							method.toString()));
				}
				key = args[index];
			}
		}
		if (Util.strIsEmpty(policy)) {
			policy = tc.getName();
		}
		// index小于0表示方法上没有注解，或者方法没有指定index，希望指定value
		if (index < 0) {
			if (Util.strIsEmpty(value)) {
				key = method.toString();
			} else {
				key = value;
			}
		}

		// 由于是方法嵌套，且分片规则不变，不需要再绑定分片参数，直接调用
		// 取当前绑定的分片参数，并与新创建的分片参数比较
		ShardingParam csp = DynamicDataSource.getBinding();
		ShardingParam nsp = new ShardingParam(policy, key);

		// 调用扩展点解析请求参数重写分片策略（优先级最高）
		if(daoShardingPolicy!=null){
			ShardingParam n=daoShardingPolicy.doPolicy(jp.getArgs());
			if(n != null) {
				nsp = n;
			}else{
				nsp = csp;
			}
		}

		// 如果已绑定了分片参数，且与新的分片参数相同，表示这是一个嵌套的分片业务方法
		if (nsp == null || nsp.equals(csp)) {
			log.debug("Proceed nested method {}, {}/{}", method, policy, key);
			// 此处传入的是csp，其session和transaction属性可能都不是空，可以被利用
			return this.proceed(method, csp, jp);
		}
		// 如果未绑定过分片参数，或者当前方法的分片参数与外层的分片参数不同，就添加新的绑定
		log.debug("Proceed method {} with policy {} and arg {}", method, policy, key);
		DynamicDataSource.bind(nsp);
		try {
			// 此处传入的是nsp，其session和transaction属性都是空，将按需开启
			return this.proceed(method, nsp, jp);
		} finally {
			DynamicDataSource.unbind();
		}
	}

	protected Object proceed(Method method, ShardingParam sp, ProceedingJoinPoint jp) throws Throwable {
		return jp.proceed();
	}

	public DaoShardingPolicy getDaoShardingPolicy() {
		return daoShardingPolicy;
	}

	public void setDaoShardingPolicy(DaoShardingPolicy daoShardingPolicy) {
		this.daoShardingPolicy = daoShardingPolicy;
	}
}
